
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>

#define REMOTE_PORT 31096
#define LOCAL_PORT  31353

static const char * const month_table[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static int recv_fd;
static void update_recv_fd(void);

static const char *get_board_name(void)
{
    static char board_name[64];
    memset(board_name, 0, sizeof(board_name));
    FILE *name_fp = fopen("/sys/firmware/devicetree/base/board_name", "r");
    if (name_fp)
    {
        int read_num = fread(board_name, 1, sizeof(board_name) - 1, name_fp);
        if (read_num > 0)
        {
            board_name[read_num] = 0;
            fclose(name_fp);
            return board_name;
        }
    }
    if (name_fp)
    {
        fclose(name_fp);
    }
    return NULL;
}

static int uname_to_version(char *cvt_str)
{
    char str[10][32];
    int idx = 0;
    memset(str, 0, sizeof(cvt_str));
    for (char *token = strtok(cvt_str, " ");
            token != NULL;
            token = strtok(NULL, " ")
        )
    {
        strncpy(str[idx], token, 32);
        idx ++;
        if (idx >= 10)
        {
            break;
        }
    }
    if (idx < 8)
    {
        return -1;
    }

    // str[3] -- month
    int month = -1;
    for (int i = 0; i < sizeof(month_table) / sizeof(month_table[0]); i ++)
    {
        if (!strcmp(month_table[i], str[3]))
        {
            month = i + 1;
            break;
        }
    }
    if (month < 0)
    {
        return -2;
    }

    // str[4] -- day
    int day = atoi(str[4]);
    if (day <= 0)
    {
        return -3;
    }

    // str[7] -- year
    int year = atoi(str[7]);
    if (year < 1900)
    {
        return -4;
    }

    sprintf(cvt_str, "r%04d%02d%02d", year, month, day);

    return 0;
}

static const char *get_board_version(void)
{
    static char _version[256];
    memset(_version, 0, sizeof(_version));

    FILE *name_fp = fopen("/proc/version", "r");
    if (!name_fp)
    {
        return NULL;
    }

    int read_num = fread(_version, 1, sizeof(_version) - 1, name_fp);
    fclose(name_fp);
 
    if (read_num < 32)
    {
        return NULL;
    }

    int jinhao_idx = read_num - 1;
    while(jinhao_idx > 0)
    {
        if (_version[jinhao_idx] == '#')
        {
            break;
        }
        jinhao_idx --;
    }
    if (read_num - jinhao_idx < 20)
    {
        return NULL;
    }
    for (int i = 0; i < read_num - jinhao_idx; i ++)
    {
        _version[i] = _version[i + jinhao_idx];
    }
    _version[read_num - jinhao_idx] = 0;
    if (uname_to_version(_version))
    {
        return NULL;
    }
    return _version;
}

static const char *get_mac_str(const char *eth_name)
{
    static char mac_str[32];

    int s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0)
    {
        return NULL;
    }

    struct ifreq ifreq;
    strncpy(ifreq.ifr_name, eth_name, IFNAMSIZ);
    int ret = ioctl(s, SIOCGIFHWADDR, &ifreq);
    close(s);

    if (ret < 0 || ifreq.ifr_hwaddr.sa_family != 1)
    {
        return NULL;
    }

    sprintf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
                (unsigned char) ifreq.ifr_hwaddr.sa_data[0],
                (unsigned char)ifreq.ifr_hwaddr.sa_data[1],
                (unsigned char) ifreq.ifr_hwaddr.sa_data[2],
                (unsigned char)ifreq.ifr_hwaddr.sa_data[3],
                (unsigned char) ifreq.ifr_hwaddr.sa_data[4],
                (unsigned char)ifreq.ifr_hwaddr.sa_data[5]);
    return mac_str;
}

static const char *get_ip_str(const char *eth_name)
{
    static char ip_str[32];

    int s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0)
    {
        return NULL;
    }

    struct ifreq ifreq;
    strncpy(ifreq.ifr_name, eth_name, IFNAMSIZ);
    int ret = ioctl(s, SIOCGIFADDR, &ifreq);
    close(s);

    if (ret < 0)
    {
        return NULL;
    }
    inet_ntop(AF_INET, &(((struct sockaddr_in *)&ifreq.ifr_addr)->sin_addr),
                ip_str, INET_ADDRSTRLEN);

    return ip_str;
}

static const char *get_ecb_info(const char *eth_name)
{
    static char result[256];
    memset(result, 0, sizeof(result));
    char buf[64];
    memset(buf, 0, sizeof(buf));

    // ip
    const char *ip = get_ip_str(eth_name);
    if (!ip)
    {
        return NULL;
    }
    sprintf(buf, "%s\n", ip);
    strcpy(result, buf);

    // mac
    const char *mac = get_mac_str(eth_name);
    if (!mac)
    {
        return NULL;
    }
    sprintf(buf, "%s\n", mac);
    strcat(result, buf);

    // version
    const char *ver = get_board_version();
    if (!ver)
    {
        return NULL;
    }
    sprintf(buf, "%s\n", ver);
    strcat(result, buf);

    // board
    const char *name = get_board_name();
    if (!name)
    {
        return NULL;
    }
    sprintf(buf, "%s\n", name);
    strcat(result, buf);

    return result;
}

static int _set_new_ip(const char *iface_name, const char *old_ip, const char *new_ip)
{
    char cmd_str[128];
    sprintf(cmd_str, "sed -i 's/%s/%s/' /etc/network/interfaces", old_ip, new_ip);
    system(cmd_str);
    sprintf(cmd_str, "ip address del %s/24 dev %s", old_ip, iface_name);
    system(cmd_str);
    sprintf(cmd_str, "ip address add %s/24 dev %s", new_ip, iface_name);
    system(cmd_str);
    usleep(1000 * 1000);
    update_recv_fd();
    return 0;
}

static int set_ip_addr(const char *eth_name, char *data)
{
    /* check if ip & mac is corrected */
    char old_ip[32], new_ip[32], mac[32];

    char *token = strtok(data, "\n");
    if (!token)
    {
        return -1;
    }
    strcpy(old_ip, token);

    token = strtok(NULL, "\n");
    if (!token)
    {
        return -2;
    }
    strcpy(mac, token);

    token = strtok(NULL, "\n");
    if (!token)
    {
        return -3;
    }
    strcpy(new_ip, token);

    if (!strcmp(old_ip, new_ip))
    {
        /* if new ip is equal old ip, do nothing */
        return 0;
    }

    if (eth_name)
    {
        /* set ip */
        _set_new_ip(eth_name, old_ip, new_ip);
    }
    return 0;
}

static int set_ip_seg(const char *eth_name, char *data)
{
    /* get the old ip */
    char old_ip[32];
    memset(old_ip, 0, sizeof(old_ip));
    const char *ip_str = get_ip_str(eth_name);
    if (!ip_str)
    {
        return -1;
    }
    strcpy(old_ip, ip_str);

    /* get the new seg */
    char new_ip[32], sub_str[8];
    memset(new_ip, 0, sizeof(new_ip));
    char *token = strtok(data, ".");
    while(token)
    {
        strcpy(sub_str, token);
        token = strtok(NULL, ".");
        if (token)
        {
            strcat(new_ip, sub_str);
            strcat(new_ip, ".");
        }
    }

    /* get the new ip */
    char old_ip_bak[32];
    memset(old_ip_bak, 0, sizeof(old_ip_bak));
    strcpy(old_ip_bak, old_ip);
    token = strtok(old_ip_bak, ".");
    while(token)
    {
        strcpy(sub_str, token);
        token = strtok(NULL, ".");
        if (!token)
        {
            strcat(new_ip, sub_str);
        }
    }

    if (!strcmp(old_ip, new_ip))
    {
        /* if new ip is equal old ip, do nothing */
        return 0;
    }

    /* set ip */
    _set_new_ip(eth_name, old_ip, new_ip);

    return 0;
}

static int udp_broadcast(const char *eth_name, const void *data, uint32_t len)
{
    if (!data || !len)
    {
        return -1;
    }

    const char *bind_ip = get_ip_str(eth_name);
    if (!bind_ip)
    {
        return -2;
    }

    int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(fd < 0)
    {
        return -3;
    }
    int opt = 1;
    int ret = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
    if (ret)
    {
        close(fd);
        return -4;
    }

    struct sockaddr_in local_addr;
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = 0;
    local_addr.sin_addr.s_addr = inet_addr(bind_ip);
    ret = bind(fd, (struct sockaddr*)&local_addr, sizeof(local_addr));
    if (ret)
    {
        close(fd);
        return -5;
    }

    struct sockaddr_in r_addr;
    memset(&r_addr, 0, sizeof(r_addr));
    r_addr.sin_family = AF_INET;
    r_addr.sin_port = htons(REMOTE_PORT);
    r_addr.sin_addr.s_addr = inet_addr("255.255.255.255"); //INADDR_BROADCAST

    int send_len = sendto(fd, data, len, 0,
                          (struct sockaddr*)&r_addr, sizeof(r_addr));
    if (len != send_len)
    {
        close(fd);
        return -6;
    }
    close(fd);
    return 0;
}

const char *get_eth_name_from_hdr(struct msghdr *hdr)
{
    static char if_name[64];

    if (!hdr)
    {
        return NULL;
    }
    memset(if_name, 0, sizeof(if_name));
    for (struct cmsghdr *cmsgtmp = CMSG_FIRSTHDR(hdr); cmsgtmp != NULL;
            cmsgtmp = CMSG_NXTHDR(hdr, cmsgtmp))
    {
        if (cmsgtmp->cmsg_level == IPPROTO_IP && cmsgtmp->cmsg_type == IP_PKTINFO)
        {
            struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsgtmp);
            // printf("idx: %d, ip: %s\n", info->ipi_ifindex, inet_ntoa(info->ipi_addr));
            char temp_str[IF_NAMESIZE];
            char *name = if_indextoname(info->ipi_ifindex, temp_str);
            if (name != NULL)
            {
                strcpy(if_name, name);
                return if_name;
            }
        }
    }
    return NULL;
}

static int prepare_recv_fd(void)
{
    int ret = 0, opt = 1, s;
    struct sockaddr_in local_addr;

    s = socket(AF_INET, SOCK_DGRAM /* | SOCK_NONBLOCK */, IPPROTO_UDP);
    if(s < 0)
    {
        return -1;
    }
    ret = setsockopt(s,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));
    if (ret)
    {
        close(s);
        return -2;
    }
    ret = setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    if (ret)
    {
        close(s);
        return -3;
    }
    ret = setsockopt(s, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt));
    if (ret)
    {
        close(s);
        return -4;
    }
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(LOCAL_PORT);
    local_addr.sin_addr.s_addr = INADDR_ANY;

    ret = bind(s,(struct sockaddr*)&local_addr,sizeof(local_addr));
    if(ret)
    {
        close(s);
        return -5;
    }

    return s;
}

static void update_recv_fd(void)
{
    if (recv_fd >= 0)
    {
        shutdown(recv_fd, SHUT_RDWR);
        close(recv_fd);
    }
    recv_fd = prepare_recv_fd();
    if (recv_fd < 0)
    {
        printf("update recv fd err: %d\n", recv_fd);
        exit(-2);
    }
}

int main(int argc,char** argv)
{
    recv_fd = -1;
    update_recv_fd();

    /* init recv_mssg header */
    struct msghdr recv_mssg;
    struct sockaddr_in client_addr;
    char control[CMSG_SPACE(64)];
    char data_buf[512];
    struct iovec iov;
    recv_mssg.msg_name = &client_addr;
    recv_mssg.msg_namelen = sizeof(client_addr);
    recv_mssg.msg_control = control;
    recv_mssg.msg_controllen = sizeof(control);
    iov.iov_base = (void *)data_buf;
    iov.iov_len = sizeof(data_buf);
    recv_mssg.msg_iov = &iov;
    recv_mssg.msg_iovlen = 1;
    recv_mssg.msg_flags = 0;

    while(1)
    {
        memset(data_buf, 0, sizeof(data_buf));
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        memset(&control, 0, sizeof(control));
        client_addr.sin_family = AF_INET;
        int recv_len = recvmsg(recv_fd, &recv_mssg, 0);
        if (recv_len > 0)
        {
            const char *eth = get_eth_name_from_hdr(&recv_mssg);
            if (!eth)
            {
                printf("get_eth_name err\n");
                continue;
            }
            // char *ip = inet_ntoa(client_addr.sin_addr);
            // int port = ntohs(client_addr.sin_port);
            // printf("[%s] receive %d bytes from %s:%d ---> \n%s\n",
            //         eth_name[active_idx], recv_len, ip, port, data_buf);
            if (!strncmp(data_buf, "GetEcbInfo\n", strlen("GetEcbInfo\n")))
            {
                const char *info =  get_ecb_info(eth);
                if (info)
                {
                    udp_broadcast(eth, info, strlen(info));
                }
                else
                {
                    printf("get_ecb_info: %s failed!\n", eth);
                }
            }
            else if (!strncmp(data_buf, "SetIpSeg\n", strlen("SetIpSeg\n")))
            {
                int ret = set_ip_seg(eth, data_buf + strlen("SetIpSeg\n"));
                if (ret)
                {
                    printf("SetIpSeg %s error: %d\n", eth, ret);
                }

            }
            else if (!strncmp(data_buf, "SetIpAddr\n", strlen("SetIpAddr\n")))
            {
                int ret = set_ip_addr(eth, data_buf + strlen("SetIpAddr\n"));
                if (ret)
                {
                    printf("SetIpAddr %s error: %d\n", eth, ret);
                }
            }
            else
            {
                printf("unknown command!\n");
            }
        }
    }
    close(recv_fd);
    return 0;
}
